﻿// --- Copyright (c) 2006-2008 Stefan Kyntchev ---
// This software is written and copyrighted by Stefan Kyntchev 
// and BeyondPod Team members. All rights are reserved.
// Author contact: support@beyondpod.mobi
// ------------------------------------------------------------------------
// This file is part of BeyondPod RSS Feed Reader and Podcast manager
// (www.codeplex.com/BeyondPod) 
// BeyondPod is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// BeyondPod is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
//  
// You should have received a copy of the GNU General Public License
// along with BeyondPod. If not, see <http://www.gnu.org/licenses/>
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using svs.Mobile.BeyondMedia.UiCore.GDI;

namespace svs.Mobile.BeyondMedia.UiCore.KineticControls
{
	public abstract class KineticListView : KineticListBase
	{
		public class ListViewItemData
		{
			public string PrimaryText;
			public string SecondaryText;
			public string Group;
			public string Note;
			public bool BoldPrimary;
			public bool LeftTrunkatePrimaryText;
			public ImageData PrimaryImage;
			public ImageData OverlayImage;
			public ImageData SecondaryImage;
			public float PercentHighlighted;
		}

		public event EventHandler BackClick;

		private readonly List<ListViewItemData> _Items = new List<ListViewItemData>();

    private static Bitmap _offGroupHeaderBmp;
		private static Graphics _offGroupHeaderGraphics;

		private bool _ShowSelectedItem = true;

		protected int LastScreenWidthUsedToCaclcTextWidths;


		private Graphics GroupHeaderGraphics
		{
			get
			{
				if(_offGroupHeaderGraphics == null)
					TryReAllocateHeaderBuffer(Settings);

				return _offGroupHeaderGraphics;
			}
		}

		public int SelectedIndex
		{
			get { return _SelectedIndex; }
			set { SelectItemInternal(value); }
		}

		public bool ShowSelectedItem
		{
			get { return _ShowSelectedItem; }
			set { _ShowSelectedItem = value; }
		}

		protected override int ItemCount
		{
			get
			{
				return _Items.Count;
			}
		}

		protected virtual string IfEmptyText
		{
			get
			{
				return "There are no items in this view";
			}
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			TryReAllocateHeaderBuffer(Settings);

			if (ShouldReloadOnResize())
				SetItems(BuildNativeControlItems(), _SelectedIndex, true);

			if (SelectedIndex != -1)
				ScrollItemIntoView(SelectedIndex);
		}

		private static void TryReAllocateHeaderBuffer(ListViewSettings  settings)
		{
			if (_offGroupHeaderBmp != null &&
						_offGroupHeaderBmp.Width == Screen.PrimaryScreen.WorkingArea.Width &&
						_offGroupHeaderBmp.Height == settings.GroupPixHeight + settings.MarginPixTop)
				return;

			try
			{
				if (_offGroupHeaderBmp != null)
					ReleaseGroupHeaderBuffer();

				_offGroupHeaderBmp = new Bitmap(Screen.PrimaryScreen.WorkingArea.Width, settings.GroupPixHeight + +settings.MarginPixTop);
				_offGroupHeaderGraphics = Graphics.FromImage(_offGroupHeaderBmp);
			}
			catch (OutOfMemoryException)
			{
				ReleaseGroupHeaderBuffer();
			}
		}

		private static void ReleaseGroupHeaderBuffer()
		{
			if (_offGroupHeaderGraphics != null)
			{
				_offGroupHeaderGraphics.Dispose();
				_offGroupHeaderGraphics = null;
			}

			if (_offGroupHeaderBmp != null)
			{
				_offGroupHeaderBmp.Dispose();
				_offGroupHeaderBmp = null;
			}
		}

		protected abstract List<ListViewItemData> BuildNativeControlItems();

		#region Drawing Methods

		protected override void DrawScreenOn(Gdi gMem, Rectangle rClip, int yListOffset)
		{
			if (rClip.Bottom > _ContentRectangle.Top || rClip.Top < _ContentRectangle.Bottom)
				DrawListOn(gMem, _ContentRectangle, yListOffset);
		}

		private void DrawListOn(Gdi gMem, Rectangle rListRect, int yOffset)
		{
			gMem.FillRect(rListRect, Settings.ListBackgroundBrush);

			if (ItemCount == 0)
			{
				DrawIfEmptyList(gMem, rListRect);
				return;
			}

			if (BackgroundHeaderVisible)
			{
				DrawBackgroundHeader(gMem, rListRect, BackgroundHeaderText);
			}

			if (BackgroundFooterVisible)
			{
				DrawBackgroundFooter(gMem, rListRect, BackgroundFooterText);
			}


			int nItem;

			int nFirstItem = nItem = yOffset < 0 ? 0 : GetPixelToItem(yOffset);

			var rItem = new Rectangle(rListRect.Left + Settings.MarginPixLeft,
																rListRect.Top,
																rListRect.Width - (Settings.MarginPixLeft + Settings.MarginPixRight),
																rListRect.Top + StartPositions[nItem] - yOffset);

			int bot = rListRect.Top + StartPositions[nItem] - yOffset;
			rItem.Height = bot - rListRect.Top;

			ListViewItemData item;

			// Draw all Items
			while (nItem < ItemCount && rItem.Bottom < rListRect.Bottom)
			{
				item = GetItemAtIndex(nItem);
			
				rItem = new Rectangle(rItem.Left, rItem.Bottom, rItem.Width, StartPositions[nItem + 1] - StartPositions[nItem]);

				DrawItemOn(gMem, item, rItem, nItem );

				// ****** Group Header
				if (IsItemNewGroup(nItem) && item.Group != null && rItem.Top >= rListRect.Top)
				{
					int headerHeight = Settings.GroupPixHeight + Settings.MarginPixTop;
					if(yOffset > 0)
						headerHeight += Settings.MarginPixBottom;

					DrawGroupHeaderOn(gMem, nItem, new Rectangle(rListRect.Left, rItem.Top, rListRect.Width, headerHeight));
				}

				// Next nItem
				nItem++;
			}

			// Special: Draw the group of the list nItem that's at the top of the list
			item = GetItemAtIndex(nFirstItem);

			if (item.Group != null && yOffset > 0 && GroupHeaderGraphics != null)
			{
				var rTopGroup = new Rectangle(rListRect.Left, 0, rListRect.Width, Settings.GroupPixHeight);

				var hdcTmp = GroupHeaderGraphics.GetHdc();
				using (var gTmp = Gdi.FromHdc(hdcTmp, Rectangle.Empty))
				{
					DrawGroupHeaderOn(gTmp, nFirstItem, rTopGroup);

					int nHeight = Settings.GroupPixHeight;
					int nBottom = nHeight;

					if (nFirstItem + 1 < ItemCount && IsItemNewGroup(nFirstItem + 1))
					{
						nBottom = Math.Min(nBottom, StartPositions[nFirstItem + 1] - yOffset);
					}

					// account for the fact that the list
					// doesn't start at the top of the screen
					nBottom += rListRect.Top;

					int nLeft = rListRect.Left;
					int nWidth = rListRect.Right - rListRect.Left;
					int nTop = nBottom - nHeight;

					gMem.BitBlt(nLeft, nTop, nWidth, nHeight, gTmp, 0, 0, Win32.ERop.SRCCOPY);

					/*if (_IsDragging || _IsScrolling)
					{
						gMem.BitBlt(nLeft, nTop, nWidth, nHeight, gTmp, 0, 0, Win32.ERop.SRCCOPY);
					}
					else
					{
						gMem.BltAlpha(nLeft, nTop, nWidth, nHeight, gTmp, 200);
					}*/
				}
				GroupHeaderGraphics.ReleaseHdc(hdcTmp);

			}
		}

		private void DrawItemOn(Gdi g, ListViewItemData item, Rectangle rItem, int nItem)
		{
			bool isSelected = ShowSelectedItem ? nItem == _SelectedIndex : false;

			if (rItem.Height > Settings.ListItemPixHeight)
				rItem = new Rectangle(rItem.Left,
				                      rItem.Top + rItem.Height - Settings.ListItemPixHeight,
				                      rItem.Width,
				                      Settings.ListItemPixHeight);

			DrawItemBackgroundOn(g, item, rItem, nItem, isSelected);

			var rSep = new Rectangle(rItem.Left, rItem.Bottom - 1, rItem.Width, 1);
			g.FillRect(rSep, Settings.ListItemSeparator);

			rItem.Inflate(Settings.ListItemContentPixMargin * -1, Settings.ListItemContentPixMargin * -1);

			DrawItemImagesOn(g, item, rItem);

			// Item Primary Text
			if (!string.IsNullOrEmpty(item.PrimaryText))
			{
				g.Font = item.BoldPrimary ? Settings.PrimaryListFontBold : Settings.PrimaryListFont;
				g.TextAlign = Win32.TextAlign.TA_LEFT;
				g.TextColor = isSelected ? Settings.ListItemSelectedTextColor : Settings.ListItemTextColor;
				g.ExtTextOut(rItem.Left + Settings.PrimaryIconPixWidth + Settings.PrimaryTextIndentPix,
											rItem.Bottom - rItem.Height + 2,
											item.PrimaryText);
			}

			// Item Secondary Text
			if (!string.IsNullOrEmpty(item.SecondaryText))
			{
				g.Font = Settings.SecondaryListFont;
				g.TextAlign = Win32.TextAlign.TA_LEFT;
				g.TextColor = isSelected ? Settings.ListItemSelectedTextColor : Settings.ListItemSecondaryTextColor;
				g.ExtTextOut(rItem.Left + Settings.PrimaryIconPixWidth + Settings.PrimaryTextIndentPix,
										 rItem.Bottom - Settings.SecondaryItemFontPixHeight - 2,
				             item.SecondaryText);
			}

			// Note text
			if (!string.IsNullOrEmpty(item.Note))
			{
				g.Font = Settings.NoteFont;
				g.TextAlign = Win32.TextAlign.TA_CENTER;
				g.TextColor = isSelected ?Settings.ListNoteSelectedTextColor : Settings.ListNoteTextColor;
				g.ExtTextOut(rItem.Right - (Settings.SecondaryIconPixWidth / 2),
										 rItem.Bottom - (rItem.Height / 2) - (Settings.NoteFontPixHeight / 2) - 1,
				             item.Note);
			}

		}

		protected virtual void DrawItemBackgroundOn(Gdi g, ListViewItemData item,  Rectangle rItem, int nItem, bool isSelected)
		{
			if (isSelected)
			{
				g.GradientFill(rItem, Settings.ItemHighLightColor1, Settings.ItemHighLightColor2,
				               Gdi.FillDirection.LeftToRight);
			}
			else
			{
				g.FillRect(rItem, nItem%2 == 0 ? Settings.ListItemBackgroundEven : Settings.ListItemBackgroundOdd);
			}
		}

		private void DrawGroupHeaderOn(Gdi g, int item, Rectangle rHeaderArea)
		{
			Rectangle rHeader = rHeaderArea;
			if(rHeaderArea.Height > Settings.GroupPixHeight)  // drawing the header as a separator between items
			{
				g.FillRect(rHeaderArea, Settings.ListBackgroundColor);

				int headerTop = rHeaderArea.Top;

				if(rHeaderArea.Height > Settings.GroupPixHeight + Settings.MarginPixTop) //has bottom and top margin
					headerTop += Settings.MarginPixBottom;

				rHeader = new Rectangle(rHeaderArea.Left, headerTop, rHeaderArea.Width, Settings.GroupPixHeight);
			}

			// Group Header background
			g.FillRect(rHeader, Settings.ListGroupBackground);

			// separator
			Rectangle rSep = new Rectangle(rHeader.Left, rHeader.Bottom - 1, rHeader.Width,1);
			g.FillRect(rSep, Settings.ListItemSeparator);

			// Draw Group Header Text
			g.Font = Settings.GroupFont;
			g.TextAlign = Win32.TextAlign.TA_LEFT;
			g.TextColor = Settings.ListGroupTextColor;
			g.ExtTextOut(rHeader.Left + Settings.GroupItemIndentPix,
									 rHeader.Top - 1 + ((Settings.GroupPixHeight - Settings.GroupFontPixHeight) / 2), GetItemAtIndex(item).Group);
		}

		private void DrawItemImagesOn(Gdi g, ListViewItemData item, Rectangle rItem)
		{
			//Main Icon		
			if (item.PrimaryImage != null)
			{
				int l = rItem.Left + Settings.PrimaryIconPixWidth/2 - item.PrimaryImage.ViewportSize.Width/2;
				int t = rItem.Bottom - Settings.PrimaryIconPixHeight + Settings.PrimaryIconPixHeight/2 -
				        item.PrimaryImage.ViewportSize.Height/2;

				if (item.PrimaryImage.TransparentColor == Color.Empty)
				{
					//This will cover the selection for smaller icons
					if( item.PrimaryImage.ViewportSize.Height < Settings.ListItemPixHeight)
					{
						g.FillRect(rItem.Left, rItem.Bottom - Settings.PrimaryIconPixHeight, Settings.PrimaryIconPixHeight-1,
						           Settings.PrimaryIconPixWidth, Settings.ListItemBackgroundOdd);
					}

					IntPtr imageDC = g.CreateCompatibleDC();
					Win32.SelectObject(imageDC, item.PrimaryImage.ImageHandle);

					g.BitBlt(l,
									 t,
									 item.PrimaryImage.ViewportSize.Width,
									 item.PrimaryImage.ViewportSize.Height,
									 imageDC,
									 item.PrimaryImage.ImageOffset.X,
									 item.PrimaryImage.ImageOffset.Y,
									 Win32.ERop.SRCCOPY);
					Win32.DeleteDC(imageDC);
				}
				else
				{
					g.TransparentImage(l,
													 t,
													 item.PrimaryImage.ViewportSize.Width,
													 item.PrimaryImage.ViewportSize.Height,
													 item.PrimaryImage.ImageHandle,
													 item.PrimaryImage.ImageOffset.X,
													 item.PrimaryImage.ImageOffset.Y,
													 item.PrimaryImage.ViewportSize.Width,
													 item.PrimaryImage.ViewportSize.Height,
													 item.PrimaryImage.TransparentColor);
				}
			}

			//Overlay Icon
			if (item.OverlayImage != null)
			{
				g.TransparentImage(rItem.Left + Settings.PrimaryIconPixWidth - item.OverlayImage.ImageSize.Width,
				                   rItem.Bottom - item.OverlayImage.ImageSize.Height,
				                   item.OverlayImage.ImageSize.Width,
				                   item.OverlayImage.ImageSize.Height,
				                   item.OverlayImage.ImageHandle,
				                   0, 0,
				                   item.OverlayImage.ImageSize.Width,
				                   item.OverlayImage.ImageSize.Height,
				                   item.OverlayImage.TransparentColor);
			}

			if (item.SecondaryImage != null)
			{
				//Secondary image area
				Rectangle rSec = new Rectangle(rItem.Right - Settings.SecondaryIconPixWidth, rItem.Bottom - Settings.SecondaryIconPixHeight,
																			 Settings.SecondaryIconPixWidth, Settings.SecondaryIconPixHeight);

				if (_MouseDownItemYPos != -1 && _MouseDownItemYPos >= rSec.Top && _MouseDownItemYPos <= rSec.Bottom)
				{
					g.GradientFill(rSec, Settings.ItemHighLightColor1, Settings.ItemHighLightColor2,
					               Gdi.FillDirection.TopToBottom);
				}

				g.TransparentImage(rSec.Left + (rSec.Right - rSec.Left)/2 - item.SecondaryImage.ImageSize.Width/2,
				                   rSec.Top + (rSec.Bottom - rSec.Top)/2 - item.SecondaryImage.ImageSize.Height/2,
				                   item.SecondaryImage.ImageSize.Width,
				                   item.SecondaryImage.ImageSize.Height,
				                   item.SecondaryImage.ImageHandle,
				                   0, 0,
				                   item.SecondaryImage.ImageSize.Width,
				                   item.SecondaryImage.ImageSize.Height,
				                   item.SecondaryImage.TransparentColor);
			}
		}


		protected void DrawIfEmptyList(Gdi g, Rectangle area)
		{
			if (ItemCount == 0)
			{
				area.Inflate(Settings.BackgroundHeaderPixMargin * -2, Settings.BackgroundHeaderPixMargin * -2);
				g.Font = Settings.SecondaryListFont;
				g.TextColor = Settings.ListGroupBackgroundColor;
				g.DrawText(IfEmptyText, new Win32.RECT(area.Left, area.Top, area.Width, area.Height), Win32.DT.CENTER);
			}
		}

		protected void DrawBackgroundHeader(Gdi g, Rectangle area, string text)
		{
			if (string.IsNullOrEmpty(text))
				return;

			area.Inflate(Settings.BackgroundHeaderPixMargin * -1, Settings.BackgroundHeaderPixMargin * -1);
			g.Pen = Settings.BackgroundHeaderPen;
			g.Brush = Settings.BackgroundHeaderBrush;
			g.RoundRect(area.Left, area.Top, area.Right, area.Top + Settings.BackgroundHeaderPixHeight, Settings.BackgroundHeaderPixMargin, Settings.BackgroundHeaderPixMargin);

			area.Inflate(Settings.BackgroundHeaderPixMargin * -1, Settings.BackgroundHeaderPixMargin * -1);
			g.Font = Settings.SecondaryListFont;
			g.TextColor = Settings.HeaderTextColor;
			g.DrawText(text, new Win32.RECT(area.Left, area.Top, area.Width, area.Height), Win32.DT.CENTER | Win32.DT.WORDBREAK);
		}

		protected void DrawBackgroundFooter(Gdi g, Rectangle area, string text)
		{
			if (string.IsNullOrEmpty(text))
				return;

			area.Inflate(-10, -10);
			g.Font = Settings.SecondaryListFont;
			g.TextColor = Settings.ListGroupTextColor;
			g.DrawText(text, new Win32.RECT(area.Left, area.Top, area.Width, area.Height), Win32.DT.RIGHT | Win32.DT.BOTTOM | Win32.DT.WORDBREAK);
		}

		#endregion
    
		protected override void OnItemSelected(int index, Point mousePosition)
		{
			Application.DoEvents();
			//Send different notificatin based if the click was on the the secondary image or the rest of the item
			if (mousePosition.X > _ContentRectangle.Right - Settings.SecondaryIconPixWidth)
				OnSecondaryIconClicked(_SelectedIndex);
			else if (mousePosition.X < _ContentRectangle.Left + Settings.PrimaryIconPixWidth)
				OnPrimaryIconClicked(_SelectedIndex);
			else
				OnItemBodyClicked(_SelectedIndex);
		}

		protected virtual void OnSecondaryIconClicked(int index)
		{
		}

		protected virtual void OnPrimaryIconClicked(int index)
		{
		}

		protected virtual void OnItemBodyClicked(int index)
		{
		}

		protected override bool IsItemNewGroup(int index)
		{
			if(GetItemAtIndex(index).Group == null)
				return false;

      if (index == 0)
				return true;

			return  GetItemAtIndex(index - 1).Group != null && GetItemAtIndex(index - 1).Group.CompareTo(GetItemAtIndex(index).Group) != 0;
		}

		protected ListViewItemData GetItemAtIndex(int inx)
		{
			return _Items[inx];
		}

		protected bool ShouldReloadOnResize()
		{
			return ItemCount > 0 && LastScreenWidthUsedToCaclcTextWidths != ClientRectangle.Width;
		}

		#region Public Methods

		public void ScrollToStart()
		{
			ScrollTo(MinScrollPosition);
		}

		public void ScrollToEnd()
		{
			ScrollTo(MaxScrollPosition);
		}

		public void UpdateItem(ListViewItemData item, int index)
		{
			if(index < 0 || index >= _Items.Count)
				return;

			_Items[index] = item;

			CalculateTextWidths(index);

			if(IsItemVisible(index))
				RefreshList();
		}
    
		public void ClearItems()
		{ 
			_Items.Clear();
			CalculateItemHeights();
			ScrollTo(0);
		}

		public void SetItemsAndShowHeader(List<ListViewItemData> items)
		{
			_Items.Clear();
			_Items.AddRange(items);
			CalculateItemHeights();
			CalculateTextWidths(-1);

			SelectedIndex = -1;
			
			ShowHeader();
		}

		public void SetItems(List<ListViewItemData> items, int selectedIndex, bool preserveScrollPosition)
		{
			_Items.Clear();
			_Items.AddRange(items);
			CalculateItemHeights();
			CalculateTextWidths(-1);

			if(!preserveScrollPosition)
				ResetScrollPosition();

			SelectedIndex = selectedIndex;
			RefreshList();
		}

		#endregion


		private void CalculateTextWidths(int itemIndex)
		{
			LastScreenWidthUsedToCaclcTextWidths = _ContentRectangle.Width;

			int min = 0, max = ItemCount;
			if(itemIndex >= 0)
			{
				min = itemIndex;
				max = itemIndex + 1;
			}
			
			if(GroupHeaderGraphics == null)
				return;

			using (var fb = Font.FromHfont((IntPtr)Settings.PrimaryListFontBold))
			{
				using (var fr = Font.FromHfont((IntPtr)Settings.PrimaryListFont))
				{
					for (int i = min; i != max; i++)
					{
						SizeF size = GroupHeaderGraphics.MeasureString(_Items[i].PrimaryText, _Items[i].BoldPrimary ? fb : fr );
						int textareaWidth = LastScreenWidthUsedToCaclcTextWidths - 
																(Settings.PrimaryIconPixWidth + Settings.PrimaryTextIndentPix) -
																(_Items[i].SecondaryImage == null && string.IsNullOrEmpty(_Items[i].Note)
						                     	? 10
																	: Settings.SecondaryIconPixWidth) -
																	10 - 
																	Settings.MarginPixLeft - Settings.MarginPixRight -
																	(Settings.ListItemContentPixMargin*2);

						float avl = (float) textareaWidth/size.Width;
						if (avl < 1)
						{
							double len = Math.Floor(_Items[i].PrimaryText.Length*avl);

							if (_Items[i].LeftTrunkatePrimaryText)
								_Items[i].PrimaryText = "..." + _Items[i].PrimaryText.Substring(_Items[i].PrimaryText.Length - (int)len , (int)len);
							else
								_Items[i].PrimaryText = _Items[i].PrimaryText.Substring(0, (int) len) + "...";
						}
					}
				}
			}
		}

		protected override string GetItemText(int ItemIndex)
		{
			var item = GetItemAtIndex(ItemIndex);
			return item != null ? item.PrimaryText : string.Empty;
		}

		protected override void OnBackKeyClicked()
		{
			if (BackClick != null)
				BackClick(this,null);
		}
		
		protected override void Dispose(bool disposing)
		{
			if(disposing)
			{
				ReleaseGroupHeaderBuffer();
			}

			base.Dispose(disposing);
		}
	}
}